(ns
  com.marzhillstudios.molehill.template
  (:use [com.marzhillstudios.util :only [add-type]]
     [com.marzhillstudios.molehill.file :only [is-hill-file? hill-file-slug]]
     [com.marzhillstudios.molehill.config :only [*site-config* with-config]]
     [com.marzhillstudios.molehill.log-util]
     [com.marzhillstudios.molehill.utils]
     [clojure.contrib.duck-streams :only [file-str]]
     [clojure.contrib.except]
     [clojure.contrib.with-ns]
     [net.cgrand.enlive-html]))

(derive ::tentry ::tstate)
(derive ::tentrylist ::tstate)
(derive ::taglist ::tstate)

(def *template-map* {})

(defn mk-template-state
  ([state] (mk-template-state *site-config* state))
  ([config state] 
    (try (cond
      (is-hill-file? state)
        (add-type ::tentry (merge config {:entries [state]}))
      (not (some #(not (is-hill-file? %)) state))
        (add-type ::tentrylist
                  (merge config {:entries state}))
      (not (some #(or (isa? (type %1) java.lang.String)
                      (not (= 3 (count %1)))) state))
        (add-type ::taglist
                  (merge config {:entries state}))
      :else
        (throw-arg "not a supported data structure for template state"))
      (catch UnsupportedOperationException e
        (throw-arg e "not a supported data structure for template state")))))

(defn template-state?
  {:tag Boolean}
  [state]
  (isa? (type state) ::tstate))

(defn state-list?
  {:tag Boolean}
  [state]
  (isa? (type state) ::tentrylist))

(defn call-template
  ([tmpl state] (call-template tmpl *site-config* state))
  ([tmpl config state]
   (let [tmpl-fun (tmpl *template-map*)]
     (cond (fn? tmpl-fun)
       (tmpl-fun (mk-template-state config state))
       :else (throw-arg (format "Configured template does not exist: [%s]"
                                tmpl))))))

(defmacro hill-page
  "Macro used for constructing a molehill template.

  A template function with the name specified will be constructed.
  The function takes a single argument which will be the templates
  state. The body is expected to translate that state into a rendered
  template. The molehill template namespace has functions to facilitate
  the deconstructing of a template state object.
 
  template-name => name of the template.
  body => template spec
  
  Molehill templates are just clojure functions."
  [template-name state-arg-name & body]
  ; the template needs use of the template api
  `(do
     ; the template fun needs access to the template api.
     ; to make this possible we use a macro that uses
     ; the namespace for us.
     (use 'com.marzhillstudios.molehill.template)
     (alter-var-root #'*template-map*
                     (fn [map_#]
                       (assoc map_# ~template-name
                              (fn [~state-arg-name]
                                (assert (template-state? ~state-arg-name))
                                ~@body))))))

; we want template to be accessible globally so that load-file can be used to evaluate them.
(with-ns 'clojure.core (use ['com.marzhillstudios.molehill.template :only ['hill-page]]))

(with-config do-load-templates
  [config]
   (info-fmt "Loading templates from: %s" (file-str
                                          (:site-template-dir config)))
   (doseq [template-file (map #(.toString %)
                              (file-seq (file-str
                                          (:site-template-dir config))))]
     (when (is-clj-file? template-file)
       (info-fmt "Loading template file: %s" template-file)
       (load-file template-file)
       (info-fmt "Templates: %s" *template-map*))))

(def load-templates
  (memoize do-load-templates))

(defmacro with-templates
  [& body]
  `(do
    (load-templates)
     ~@body))

(def #^{:private true} page-resource (or (.getResource
                         (clojure.lang.RT/baseLoader)
                         "html/page.html")
                       "resources/html/page.html"))

(def #^{:private true} feed-resource (or (.getResource
                         (clojure.lang.RT/baseLoader)
                         "html/feed.rss")
                       "resources/html/feed.rss"))

(defn- create-tag-link
  [tag]
  (cond
    (isa? (type tag) clojure.lang.IPersistentVector)
      (format "<a class='%s' href='%s'>%s</a>"
              (str "tagsize" (nth tag 2))
              (str "/" (nth tag 1) "/")
              (nth tag 1))
    :else
      (format "<a class='%s' href='%s'>%s</a>"
              (str "tagsize" 1)
              (str "/" tag "/")
              tag)))

(defn- create-tag-links
  [tags]
  (for [tag tags]
    (create-tag-link tag)))

(defn- do-tags
  [tags]
    (html-snippet (apply str (mapcat #(str % " ") (create-tag-links tags)))))

(defn- do-tags-append
  [tags]
    (append (do-tags tags)))

(defn- do-tags-content
  [tags]
    (content (do-tags tags)))

(defn- mk-link
  [txt href]
  (html-snippet (format "<a href='%s'>%s</a>" href txt)))

(defn- do-posts
  [posts]
    (clone-for [post (take 5 posts)]
          [:h1] (content (mk-link (:title post)
                                  (str "/entries/" (hill-file-slug post) "/")))
          [:div.datetime :span.post-time] (content (:date (:date post)))
          [:div.post-body] (html-content ((:parsed-content post)))
          [:div.tags] (do-tags-append (:tags post))))

(defn- do-feed-items
  [posts]
    (clone-for [post posts]
          [:title] (content (:title post))
          [:link] (content (str "/entries/" (hill-file-slug post) "/"))
          [:category] (do-tags-append (:tags post))
          [:description] (html-content ((:parsed-content post)))
          [:pubDate] (html-content (:date (:date post)))))

(deftemplate entry-page page-resource [state]
             [:head :title] (content (str (:site-name state) "-"
                                          (:title (first (:entries state)))))
             [:div#topbar :h1#title] (content (mk-link (str (:site-name state))
                                                       "/"))
             [:div#content :div.post] (do-posts (:entries state))
             ; TODO(jwall): add link to molehill site.
             [:div#powered-by] (content "Powered By molehill"))

(deftemplate index-page page-resource [state]
             [:head :title] (content (str (:site-name state)))
             [:div#topbar :h1#title] (content (mk-link (str (:site-name state))
                                                       "/"))
             [:div#content :div.post] (do-posts (:entries state))
             ; TODO(jwall): add link to molehill site.
             [:div#powered-by] (content "Powered By molehill"))

(deftemplate feed-page feed-resource [state]
             [:channel :title] (content (str (:site-name state)))
             ; TODO(jwall): should the below in the config?
             [:channel :description] (content (str ""))
             [:channel :copyright] (content (str ""))
             [:channel :language] (content (str "en-us"))
             [:channel :lastBuildDate] (content (str ""))
             [:channel :webMaster] (content (str ""))
             [:channel :generator] (content (str "molehill-0.0.1"))
             [:item] (do-feed-items (:entries state))
             ; TODO(jwall): add link to molehill site.
             [:div#powered-by] (content "Powered By molehill"))

(deftemplate tag-landing-page page-resource [state]
             [:head :title] (content (str (:site-name state)))
             [:div#topbar :h1#title] (content (mk-link (str (:site-name state))
                                                       "/"))
             [:div#content :div.post] (do-posts (:entries state))
             ; TODO(jwall): add link to molehill site.
             [:div#powered-by] (content "Powered By molehill"))

(deftemplate tag-page page-resource [state]
             [:head :title] (content (str (:site-name state)))
             [:div#topbar :h1#title] (content (mk-link (str (:site-name state))
                                                       "/"))
             [:div#content :div.post] (do-tags-content (:entries state))
             ; TODO(jwall): add link to molehill site.
             [:div#powered-by] (content "Powered By molehill"))

(hill-page :sample-index-tmpl index-state
               (apply str (index-page index-state)))

(hill-page :sample-feed-tmpl index-state
               (apply str (feed-page index-state)))

(hill-page :sample-tag-tmpl index-state
               (apply str (tag-page index-state)))

(hill-page :sample-tag-landing-tmpl index-state
               (apply str (tag-landing-page index-state)))

(hill-page :sample-entry-tmpl index-state
               (apply str (entry-page index-state)))

